package com.witframework.soa.transform;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import org.apache.commons.codec.EncoderException;

import com.witframework.WitException;
import com.witframework.core.BaseEntity;
import com.witframework.soa.util.ClassTypeUtil;
import com.witframework.soa.util.XmlBuilder;
import com.witframework.util.StringDigest;

/**
 * java bean装换为xml的类，将支持的可以表达为XML的对象转换为XML文档的表示形式。
 * <p>
 * 支持的数据类型：全部基本数据类型(char、byte、int、long、short、double、float、boolean)
 * 和其包装对象(Char、Byte、Integer、Long、Short、Float、Double、Boolean)，
 * String，BaseEntity类型，以及它们的数组类型(char[]、int[]、byte[]等)；
 * List、Map类型.
 * </p>
 * @author wuxinyang
 * @see com.westerasoft.wsframework.core.BaseEntity
 */
public class BeanToXml {

	private Map<String, String> typeMap=new HashMap<String, String>();
	private boolean compress=false;


	public void setCompress(boolean compress){
		this.compress=compress;
	}
	private String registerType(String type){
		if(compress){
			String typemap=typeMap.get(type);
			if(typemap==null){
				typemap=String.valueOf(typeMap.size()+1);
				typeMap.put(type,typemap);
			}
			return typemap;
		}else{
			if(type.indexOf("$$EnhancerByCGLIB")>0){
				return type.substring(0,type.indexOf("$$EnhancerByCGLIB"));
			}else{
				return type;
			}
		}
	}
	/**
	 * 执行转换方法
	 * @param bean 支持的可以表达为XML的对象
	 * @return xml的字符串表示
	 */
	public String parse(Object bean){
		if(bean==null) return null;
		XmlBuilder xml = new XmlBuilder();
		xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		if (compress) {
			xml.elementStart("root").close();
			xml.elementStart("data").close();
			xml.append(toXml(bean, bean.getClass().getSimpleName()));
			xml.elementEnd("data");
			xml.append(typeXml());
			xml.elementEnd("bean");
			return xml.toString();
		} else {
			return xml.append(toXml(bean, bean.getClass().getSimpleName())).toString();
		}

	}
	private String typeXml(){
		XmlBuilder xml = new XmlBuilder();
		xml.elementStart("mapping").close();
		Iterator iters=typeMap.keySet().iterator();
		while(iters.hasNext()){
			String key=(String)iters.next();
			String value=(String)typeMap.get(key);
			xml.elementStart("item").attribute("key",value).attribute("type",key).xclose();
		}
		xml.elementEnd("mapping");
		return xml.toString();
	}
	/**
	 *
	 * @param bean 支持的可以表达为XML的对象
	 * @param name -节点名称,或xml文档的根节点(root)名称
	 * @return xml的字符串表示
	 */
	protected String toXml(Object bean, String name) {
		return toXml(bean,name,null,null);
	}
	/**
	 *
	 * @param bean 支持的可以表达为XML的对象
	 * @param name -节点名称,或xml文档的根节点(root)名称
	 * @return xml的字符串表示
	 */
	protected String toXml(Object bean, String name,String attribute) {
		return toXml(bean,name,null,attribute);
	}
	/**
	 *
	 * @param bean 支持的可以表达为XML的对象
	 * @param name 节点名称
	 * @param typeName 节点的类型名称，如果为null则回去bean的类型名称
	 * @return
	 */
	protected String toXml(Object bean, String name,String typeName,String attribute) {
		if(isCGLIBName(name)||bean==null) return "";
		XmlBuilder xml = new XmlBuilder();
		Class classType=bean.getClass();
		if (classType.isPrimitive() || ClassTypeUtil.isBaseType(classType)) {
			if(typeName==null){
				typeName=classType.getSimpleName();
			}
			if(classType==String.class)
				bean=packString(String.valueOf(bean));
			String type=registerType(typeName);
			xml.elementStart(name).attribute("type",type);
			if(attribute!=null) xml.append(attribute);
			xml.close();
			xml.append(bean).elementEnd(name).toString();
		}else if(List.class.isAssignableFrom(classType)){
			xml.append(listToXml(bean,bean.getClass().getSimpleName()));
		}else if(Map.class.isAssignableFrom(classType)){
			xml.append(mapToXml(bean,bean.getClass().getSimpleName()));
		}else if (Collection.class.isAssignableFrom(classType)) {
			xml.append(listToXml(bean, bean.getClass().getSimpleName()));
		}
		else {
			if (name != null){
				String type=registerType(bean.getClass().getName());
				xml.elementStart(name).attribute("type",type );
				if(attribute!=null) xml.append(attribute);
				xml.close();
			}			
			for (Class superClass = bean.getClass().getSuperclass(); superClass != null
					&& superClass != BaseEntity.class; superClass = superClass
					.getSuperclass()) {
				try {
					Field[] fs = superClass.getDeclaredFields();
					for (Field f : fs) {
						String fname = f.getName();
						Object fvalue =  ClassTypeUtil.getFieldValue(bean, fname);
						String x = toXml(fvalue, fname,null,null);
						xml.append(x);
					}
				} catch (Exception e) {
				}
			}
			Field[] fields = bean.getClass().getDeclaredFields();
			for (Field field : fields) {
				String fieldName = field.getName();
				Class type = field.getType();
				Object value =  ClassTypeUtil.getFieldValue(bean, fieldName);
				if(value!=null)
				if (List.class.isAssignableFrom(type)) {
					xml.append(listToXml(value, fieldName));
				} else if (Map.class.isAssignableFrom(type)) {
					xml.append(mapToXml(value, fieldName));
				} else if (type.isArray()) {
					xml.append(arraysToXML(value, fieldName));
				} else if (BaseEntity.class.isInstance(value)) {
					xml.append(entityToXml(value, fieldName));
				} else {
					if(type==String.class)
						value=packString(String.valueOf(value));
					String typeString=registerType(value.getClass().getSimpleName()) ;
					if(!isCGLIBName(fieldName)){
					xml.elementStart(fieldName).attribute("type",
							typeString).close().append(
							value).elementEnd(fieldName).toString();
					}
				}
			}
			if (name != null)
				xml.elementEnd(name);
		}
		return xml.toString();
	}
	private String packString(String value){

		if(!Pattern.matches("[^\\p{Punct}]*", value))
			return "<![CDATA["+value+"]]>";
		else
			return value;
	}
	private String arraysToXML(Object bean,String name){
		if(bean==null||isCGLIBName(name)) return "";
		XmlBuilder xml=new XmlBuilder();
		xml.elementStart(name).attribute("type", "arrays").close();
		Class componentType=bean.getClass().getComponentType();
		String itemTypeName=null;
		if(ClassTypeUtil.isBaseType(componentType)||ClassTypeUtil.isBasePrimitiveType(componentType.getName())){
			itemTypeName=componentType.getSimpleName();
			if( ClassTypeUtil.isNumber(itemTypeName) ){
				String type=registerType(itemTypeName);
				xml.elementStart("item").attribute("type", type ).close();
			}
		}
		if(ClassTypeUtil.isByte(itemTypeName)){
			try {
				xml.append(toXml(StringDigest.encodeBytes((byte[])bean), "item",itemTypeName,null));
			} catch (EncoderException e) {
				throw new WitException(e);				
			}
		}else{
		int length=Array.getLength(bean);
		for(int i=0;i<length;i++){
			Object value=Array.get(bean,i);
			if( ClassTypeUtil.isNumber(itemTypeName) ){
				xml.append(value);
				if(i<length-1)
					xml.append(",");
			}
			else{
				if(itemTypeName==null){
					if(ClassTypeUtil.isBaseType(value.getClass()))
						itemTypeName=value.getClass().getSimpleName();
					else
						itemTypeName=value.getClass().getName();
				}
			xml.append(toXml(value, "item",itemTypeName,null));
			}
		}
		}
		if( ClassTypeUtil.isNumber(itemTypeName) ){
			xml.elementEnd("item");
		}
		xml.elementEnd(name);
		return xml.toString();

	}

	private String listToXml(Object bean,String name) {
		if(isCGLIBName(name)||bean==null) return "";
		String type=registerType(bean.getClass().getName());
		XmlBuilder xml=new XmlBuilder();
		xml.elementStart(name).attribute("type", type ).close();
		List list=(List)bean;
		for(int i=0;i<list.size();i++){
			xml.append(toXml(list.get(i), "item",null," index=\""+i+"\""));
		}
		xml.append("</").append(name).append(">");
		return xml.toString();
	}

	private String entityToXml(Object bean,String name) {
		if(isCGLIBName(name)||bean==null) return "";

		String type=registerType(bean.getClass().getName());

		XmlBuilder xml=new XmlBuilder();
		xml.elementStart(name).attribute("type",type ).close();
		xml.append(toXml(bean,null,null,null));
		xml.elementEnd(name);
		return xml.toString();
	}

	private String mapToXml(Object bean,String name) {
		if(isCGLIBName(name)||bean==null) return "";

		String type=registerType(bean.getClass().getName());

		XmlBuilder xml=new XmlBuilder();
		xml.elementStart(name).attribute("type",type ).close();
		Map map=(Map)bean;
		Iterator keys=map.keySet().iterator();
		int i=0;
		while(keys.hasNext()){
			String keyname=(String)keys.next();
			Object value=map.get(keyname);
			xml.append(toXml(value,keyname,null," index=\""+i+"\""));
			i++;
		}
		xml.append("</").append(name).append(">");
		return xml.toString();
	}
	boolean isCGLIBName(String name){
		if(name==null) return false;
		return name.startsWith("CGLIB$");
	}
	
	public static void main(String[] args){
		String xml=new BeanToXml().parse(java.util.Arrays.asList(new String[]{"123","234"}));
		System.out.println(xml);
	}
}
